package erfgame.core;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;

public class PollingExecutor extends Thread implements Executor {

	private static final Logger log = Logger.getLogger( PollingExecutor.class.getName() ); 
	
	private long delay;
	private Queue<Runnable> commands;
	private ArrayList<Runnable> waitingCommands;
	private boolean running;
	private int maxQueueLength;
	
	public PollingExecutor( long delay ) {
		this.delay = delay;
		this.commands = new LinkedList<Runnable>();
		this.waitingCommands = new ArrayList<Runnable>();
	}
	
	public int getMaxQueueLength() {
		return this.maxQueueLength;
	}
	
	public void setMaxQueueLength( int maxQueueLength ) {
		this.maxQueueLength = maxQueueLength;
	}
	
	public void execute(Runnable command) {
		if( maxQueueLength > 0 ) {
			while( waitingCommands.size() >= maxQueueLength ) {
				// wait until it gets smaller (pretty horrible, but I guess it is the polling executor!)
				log.info("queue length of "+maxQueueLength+" exceeded (size "+waitingCommands.size()+"), waiting until it drops before adding command");
				try {
					Thread.sleep( delay );
				} catch( InterruptedException ex ) {
					
				}
			}
		}
		this.waitingCommands.add( command );
		if( !running ) {
			startPolling();
		}
	}
	
	public void startPolling() {
		this.running = true;
		start();
	}
	
	protected void addWaitingCommands( Queue<Runnable> commands, ArrayList<Runnable> waitingCommands ) {
		commands.addAll(waitingCommands);
	}
	
	@SuppressWarnings("unchecked")
	public Operation stopPolling() {
		return new SimpleAbstractOperation<Void, Throwable>( "stopping polling" ) {
			@Override
			protected Void doPerform() throws Throwable {
				synchronized( PollingExecutor.this ) {
					if( running ) {
						running = false;
						try {
							PollingExecutor.this.wait();
						} catch( InterruptedException ex ) {
							log.log( Level.INFO, "thread interrupted", ex );
						}
					}
				}
				return null;
			}
		};
	}

	public void run() {
		while( running || commands.size() > 0 || waitingCommands.size() > 0 ) {
			try {
				Thread.sleep( delay );
			} catch( InterruptedException ex ) {
				
			}
			// TODO : maybe have two buffers and swap them around instead of creating new ones
			ArrayList<Runnable> waitingCommands = this.waitingCommands;
			this.waitingCommands = new ArrayList<Runnable>();
			addWaitingCommands( commands, waitingCommands );
			
			Runnable toRun;
			while( ( toRun = this.commands.poll() ) != null ) {
				try {
					toRun.run();
				} catch( Throwable ex ) {
					log.log( Level.SEVERE, "unable to process "+toRun, ex );
				}
				//log.info( "outstanding entries : "+commands.size() );
			}
		}
		synchronized( this ) {
			this.notify();
		}
	}
}
